﻿using Microsoft.AspNetCore.Mvc.ActionConstraints;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using System.Text;

namespace 动态api
{
    public class ApplicationServiceConvention : IApplicationModelConvention
    {
        //微软定义：调用以将约定应用于该ApplicationModel约定
        public void Apply(ApplicationModel application)
        {
            //前置操作：ApplicationServiceControllerFeatureProvider扩展已经实现将实现IApplicationService接口的类视为控制器
            //遍历控制器
            foreach (var controller in application.Controllers)
            {
                //Type.IsAssignableFrom(Type) 方法
                //如果满足下列任一条件，则为 true：
                //c 和当前实例表示相同类型。
                //c 是从当前实例直接或间接派生的。 如果继承于当前实例，则 c 是从当前实例直接派生的；如果继承于从当前实例继承的接连一个或多个类，则 c 是从当前实例间接派生的。
                //当前实例是实现 的 c 接口。
                //c 是一个泛型类型参数，并且当前实例表示 c 的约束之一。
                //c 表示值类型，当前实例表示 Nullable<c> Visual Basic) Nullable(Of c) 中的(。
                //如果不满足上述任何一个条件或者 c 为 false，则为 null。

                //判断控制器是否实现IApplicationService类或是其子类
                if (typeof(IApplicationService).IsAssignableFrom(controller.ControllerType))
                {
                    ConfigureApiExplorer(controller);
                    ConfigrueSelector(controller);
                }
            }
        }

        private static void ConfigureApiExplorer(ControllerModel controller)
        {
            if (!controller.ApiExplorer.IsVisible.HasValue)
                controller.ApiExplorer.IsVisible = true;

            foreach (var action in controller.Actions)
            {
                if (!action.ApiExplorer.IsVisible.HasValue)
                {
                    action.ApiExplorer.IsVisible = true;
                }
            }
        }

        private void ConfigrueSelector(ControllerModel controller)
        {
            RemoveEmptySelectors(controller.Selectors);

            if (controller.Selectors.Any(select => select.AttributeRouteModel != null))
                return;

            foreach (var action in controller.Actions)
            {
                ConfigrueSelector(action);
            }
        }

        private void ConfigrueSelector(ActionModel action)
        {
            RemoveEmptySelectors(action.Selectors);

            if (action.Selectors.Count <= 0)
                AddServiceSelector(action);
            else
                NormalizeSelectorRoutes(action);
        }

        private void NormalizeSelectorRoutes(ActionModel action)
        {
            foreach (var selector in action.Selectors)
            {
                if (selector.AttributeRouteModel == null)
                {
                    var template = new Microsoft.AspNetCore.Mvc.RouteAttribute(GetRouteTemplate(action));
                    selector.AttributeRouteModel = new AttributeRouteModel(template);
                }

                if (selector.ActionConstraints.OfType<HttpMethodActionConstraint>().FirstOrDefault()?.HttpMethods?.FirstOrDefault() == null)
                    selector.ActionConstraints.Add(new HttpMethodActionConstraint(new[] { GetHttpMethod(action) }));
            }
        }

        private static void RemoveEmptySelectors(IList<SelectorModel> selectors)
        {
            for (var i = selectors.Count - 1; i >= 0; i--)
            {
                var selector = selectors[i];
                if (selector == null &&
                    (selector.ActionConstraints == null || selector.ActionConstraints.Count <= 0) &&
                    (selector.EndpointMetadata == null || selector.EndpointMetadata.Count <= 0))
                {
                    selectors.Remove(selector);
                }
            }
        }

        private void AddServiceSelector(ActionModel action)
        {
            var template = new Microsoft.AspNetCore.Mvc.RouteAttribute(GetRouteTemplate(action));
            var selector = new SelectorModel
            {
                AttributeRouteModel = new AttributeRouteModel(template)
            };
            selector.ActionConstraints.Add(new HttpMethodActionConstraint(new[] { GetHttpMethod(action) }));
            action.Selectors.Add(selector);
        }

        private static string GetHttpMethod(ActionModel action)
        {
            //如果一个api的名称以get开头则视为get方法，否则全视为post方法
            var actionName = action.ActionName;
            if (actionName.ToLower().StartsWith("get"))
                return "GET";

            return "POST";
        }

        private string GetRouteTemplate(ActionModel action)
        {
            if (action.Attributes != null && action.Attributes.Count > 0)
            {
                foreach (var item in action.Attributes)
                {
                    if (item is RouteAttribute attribute)
                    {
                        return attribute.Path;
                    }
                }
            }

            var routeTemplate = new StringBuilder();

            //routeTemplate.Append("api");
            var names = action.Controller.ControllerType.Namespace.Split(",");
            if (names.Length > 2)
            {
                routeTemplate.Append(names[^2]);
            }

            //Controller
            var controllerName = action.Controller.ControllerName;
            if (controllerName.ToLower().EndsWith("service"))
                controllerName = controllerName[0..^7];

            routeTemplate.Append($"/{controllerName}");

            //Action
            var actionName = action.ActionName;
            if (actionName.ToLower().EndsWith("async"))
                actionName = actionName[..^"async".Length];

            if (!string.IsNullOrEmpty(actionName))
                routeTemplate.Append($"/{actionName}");

            return routeTemplate.ToString();
        }
    }
}